grammar Mint;

options {
    output = AST;
    backtrack = true;
    memoize = true;
}

tokens
{
  COMPILATION_UNIT;
  CLASS_DECLARATION;
  SUPER;
  CLASSBODY;
  CONSTRUCTOR;
  FIELD;
  METHOD;
  PRIMITIVE_TYPE;
  CLASS_TYPE;
  ARRAY_TYPE;
  MODIFIER;
  ASSIGN;
  PREINCREMENT;
  PREDECREMENT;
  POSTINCREMENT;
  POSTDECREMENT;
  THIS;
  SUPER;
  EXPR;
  DOT;
  ARRAYACCESS;  
  CALLMETHOD;
  NEW;
  NEWARRAY;
  ARRAYCREATIONEXPR;
  CAST;
  METHODHEADER;
  ARGUMENTLIST;
  FORMALPARAMETERLIST;
  FORMALPARAMETER;
  VOID;
  CONSTRUCTORBODY;
  BLOCK;
  EXPLICITCONSTRUCTORINV;
  LOCALVARIABLEDECLARATION;
  EMPTYSTMT;
  EXPRSTMT;
  IF;
  ELSEIF;
  ELSE;
  WHILE;
  DOWHILE;
  RETURN;
  POSITIVE;
  NEGATIVE;
  SELECTOR;
  PRIMARY;
}

@lexer::header { package mint;
     import java.util.List;
     import java.util.LinkedList;
}
@lexer::members {
	private Stack<Integer> indentation = initStack();
	
	public int getIndentation() {
	  return indentation.peek();
	}
	      
	static Stack<Integer> initStack() {
	  Stack<Integer> s = new Stack<Integer>();
	  s.push(0);
	  return s;
	}
	
	List<Token> tokens = new LinkedList<Token>();
	
	@Override
	public void emit(Token token) {
	  state.token = token;
	  tokens.add(token);
	}
	
	@Override
	public Token nextToken() {
	  super.nextToken();
	  if ( tokens.size()==0 ) {
	      return Token.EOF_TOKEN;
	  }
	  return tokens.remove(0);
  }

}

@parser::header { package mint;
     import java.util.List;
     import java.util.ArrayList; }
@parser::members {
  List<String> errors = new ArrayList<String>();
  
  public List<String> getErrorList()
  {
    return errors;
  }

  @Override
  public void displayRecognitionError(String[] tokenNames,
                                        RecognitionException e) {
          String msg = getErrorMessage(e, tokenNames);
          // Now do something with hdr and msg...*/
          
          errors.add("error " + e.line + " " + msg);
  }
}


compilationUnit : (classDeclaration | NewLine)* -> ^(COMPILATION_UNIT (classDeclaration)*);

indent :  Indent;

dedent  : Dedent | EOF;

newline : NewLine | EOF;

literal : IntegerLiteral | FloatingPointLiteral | CharLiteral | StringLiteral | 'true' | 'false' | 'null';

type : primitiveType -> ^(PRIMITIVE_TYPE primitiveType) | referenceType;

primitiveType : 'int' | 'float' | 'boolean';

referenceType : classType -> ^(CLASS_TYPE classType) | arrayType -> ^(ARRAY_TYPE arrayType);

classType : name;

arrayType : (primitiveType | name) ('[' ']'!)+ ;

name : Identifier;

modifiers : 'public' | 'protected' | 'private';

classDeclaration : (modifiers)? 'class' Identifier (thesuper)? classBody -> ^(CLASS_DECLARATION (^(MODIFIER modifiers))? Identifier (thesuper)? classBody);

thesuper : 'extends' classType -> ^(SUPER classType);

classBody : indent classBodyDeclarations dedent -> ^(CLASSBODY classBodyDeclarations);

classBodyDeclarations : (classBodyDeclaration)+ | emptyStmt;

classBodyDeclaration : classMemberDeclaration | constructorDeclaration;

classMemberDeclaration : fieldDeclaration | methodDeclaration;

fieldDeclaration : (modifiers)? type variableDeclarators newline -> ^(FIELD (^(MODIFIER modifiers))? type variableDeclarators);

variableDeclarators : variableDeclarator (',' variableDeclarator)* -> (variableDeclarator)+;

variableDeclarator : Identifier  ('=' expr)? -> ^(Identifier (expr)?);

expr : conditionalExpr (assignmentOperator expr)? -> ^(EXPR conditionalExpr (^(ASSIGN assignmentOperator expr))?);

assignmentOperator : '=' | '*=' | '/=' | '%=' | '+='  | '-=' | '&=' | '^=' | '|=';

conditionalExpr : conditionalOrExpr;

conditionalOrExpr : conditionalAndExpr ('||' ^conditionalAndExpr)*;

conditionalAndExpr : inclusiveOrExpr ('&&' ^inclusiveOrExpr)*;

inclusiveOrExpr : exclusiveOrExpr ('|' ^exclusiveOrExpr)*;

exclusiveOrExpr : andExpr ('^' ^andExpr)*;

andExpr : equalityExpr ('&' ^equalityExpr)*;

equalityExpr : relationalExpr (('==' | '!=') ^relationalExpr)*;

relationalExpr : additiveExpr (( '<' | '>' | '<=' | '>=' ) ^additiveExpr)*;

additiveExpr : multiplicativeExpr (( '+' | '-' ) ^multiplicativeExpr)*;

multiplicativeExpr : unaryExpr (( '*' | '/' | '%') ^unaryExpr)*;

unaryExpr : '+'unaryExpr -> ^(POSITIVE unaryExpr)
    |   '-' unaryExpr -> ^(NEGATIVE unaryExpr)
    | '++' unaryExpr -> ^(PREINCREMENT unaryExpr)
    | '--' unaryExpr -> ^(PREDECREMENT unaryExpr)
    |   unaryExprNotPlusMinus;

unaryExprNotPlusMinus :   '~' unaryExpr -> ^('~' unaryExpr)
      | '!' unaryExpr -> ^('!' unaryExpr)
      |  castExpr 
      |  primary ('++' -> ^(POSTINCREMENT primary)
      		|'--' -> ^(POSTDECREMENT primary))
      | primary (selector)* -> ^(PRIMARY primary ^(SELECTOR selector)*);

primary : '(' expr ')' -> expr
      | 'this' (memberAccess)*(identifierSuffix)? -> ^(THIS (memberAccess)*(identifierSuffix)?)
      | name   (memberAccess)*(identifierSuffix)? -> ^(name (memberAccess)*(identifierSuffix)?)
      | 'super' superSuffix -> ^(SUPER superSuffix)
      | literal
      | classInstanceCreationExpr
      | arrayCreationExpr;

superSuffix  :  memberAccesWithMethod;

selector  : memberAccesWithMethod 
  |  '[' expr ']' -> ^(ARRAYACCESS expr);

memberAccess : '.' Identifier -> ^(DOT Identifier);

memberAccesWithMethod   : memberAccess(callMethod)? -> memberAccess (callMethod)?;

callMethod : '(' (argumentList)? ')' -> ^(CALLMETHOD argumentList?);
    
identifierSuffix :  '[' expr ']' ->^(ARRAYACCESS expr)
       | callMethod -> callMethod;       

arrayCreationExpr : 'new' (primitiveType '[' expr ']'('[' expr ']')* -> ^(NEWARRAY ^(PRIMITIVE_TYPE primitiveType) ^(ARRAYCREATIONEXPR expr)+)
        | classType     '[' expr ']'('[' expr ']')* -> ^(NEWARRAY ^(CLASS_TYPE classType) ^(ARRAYCREATIONEXPR expr)+));

classInstanceCreationExpr : 'new' classType callMethod -> ^(NEW classType callMethod);

castExpr : '(' type ')' unaryExpr -> ^(CAST type unaryExpr);

methodDeclaration : methodHeader block -> ^(METHOD methodHeader block);

methodHeader : (modifiers)?  ( type methodDeclarator  -> ^(METHODHEADER (^(MODIFIER modifiers))? type methodDeclarator)
          | 'void' methodDeclarator -> ^(METHODHEADER (^(MODIFIER modifiers))? VOID methodDeclarator));

methodDeclarator : Identifier '(' (formalParameterList)? ')' -> Identifier ^(FORMALPARAMETERLIST (formalParameterList)?);

formalParameterList : formalParameter (',' formalParameter)* -> (formalParameter)+;

formalParameter : type Identifier -> ^(FORMALPARAMETER type Identifier);

constructorDeclaration : (modifiers)? constructorDeclarator constructorBody -> ^(CONSTRUCTOR (^(MODIFIER modifiers))? constructorDeclarator constructorBody);

constructorDeclarator : Identifier '(' (formalParameterList)? ')' -> Identifier ^(FORMALPARAMETERLIST (formalParameterList)?);

constructorBody : indent (explicitConstructorInvocation)? blockStmts dedent -> ^(CONSTRUCTORBODY (explicitConstructorInvocation)? blockStmts);    

explicitConstructorInvocation : 'this''(' (argumentList)?')' newline  -> ^(EXPLICITCONSTRUCTORINV THIS argumentList?)
        | 'super' '(' (argumentList)?')' newline -> ^(EXPLICITCONSTRUCTORINV SUPER argumentList?);

argumentList : expr ( ',' expr )* -> ^(ARGUMENTLIST (expr)+);

//abstractMethodDeclaration : methodHeader (NewLine)+;

block : indent blockStmts dedent -> ^(BLOCK blockStmts);

blockStmts : ( blockStmt )+ | emptyStmt;

blockStmt : localVariableDeclarationStmt | stmt;

localVariableDeclarationStmt : type variableDeclarators newline -> ^(LOCALVARIABLEDECLARATION type variableDeclarators);

stmt : expr newline -> ^(EXPRSTMT expr)
  | doStmt | returnStmt | ifThenStmt | whileStmt;
  
emptyStmt : 'pass' newline -> ^(EMPTYSTMT);

ifThenStmt : 'if' '(' expr ')' block (elseifStmt)* (elseStmt)? ->
        ^(IF expr block (elseifStmt)* (elseStmt)?);

elseifStmt 
	: 'elsif' '(' expr ')' block -> ^(ELSEIF expr block);
elseStmt
	: 'else' block -> ^(ELSE block);

whileStmt : 'while' '(' expr ')' block -> ^(WHILE expr block);

doStmt : 'do' block 'while' '(' expr ')' newline -> ^(DOWHILE block expr);     

returnStmt : 'return' (expr)? newline -> ^(RETURN (expr)?);

INDENTATION : (NL WS)* NL (ws=WS)
{
        int start = getCharIndex();
        int startLine = getLine();
        int startCharPos = getCharPositionInLine();

        int indentsize = ws.getText().replace("\t", "    ").replace("<EOF>", "").length();

        CommonToken token = null;
  
        if (indentsize > getIndentation()) {
           token = new CommonToken(Indent); 
           token.setText("Indent\n");
           indentation.push(indentsize);
           
           token.setStartIndex(start);
           token.setLine(startLine);
           token.setCharPositionInLine(startCharPos);
        
           emit(token);
        }
        else if( indentsize == getIndentation()) {
            token = new CommonToken(NewLine);
            token.setText("\n");
            token.setStartIndex(start);
            token.setLine(startLine);
            token.setCharPositionInLine(startCharPos);
        
            emit(token);
        }
        else {
            boolean nl = false;
        
        while (indentsize < getIndentation()) {
        
            if (!nl) {
              token = new CommonToken(NewLine);
              token.setText("NewLine");
              token.setStartIndex(start);
              token.setLine(startLine);
              token.setCharPositionInLine(startCharPos);
        
              emit(token);
              nl = true;
             }
      
                token = new CommonToken(Dedent);
                token.setText("Dedent");
                indentation.pop();
                token.setStartIndex(start);
                token.setLine(startLine);
                token.setCharPositionInLine(startCharPos);
        
                emit(token);
            }
        }
};

Identifier 
  :  ('a'..'z' | 'A'..'Z' | '_')('a'..'z' | 'A'..'Z' | '0'..'9' | '_')*;

StringLiteral
  : '"' (~ '"')* '"';
  
FloatingPointLiteral
  : ('0'..'9')+'.'('0'..'9')+ | ('0'..'9')+'.' | '.'('0'..'9')+;

CharLiteral
  : '\''('a'..'z' | 'A'..'Z' | '0'..'9')'\'';
  
IntegerLiteral
  : ('0'..'9')+;

WS : ('\t' | ' ')* { $channel=HIDDEN; };

Error : .;

fragment NL
  : '\r'? '\n';

fragment Indent 
	:	;
fragment Dedent
	:	;
fragment NewLine
	:	;